﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Reflection;
using System.Diagnostics;

using Bio;
using Bio.IO;
using Bio.IO.GenBank;
using Microsoft.Win32;


namespace SequenceViewer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Dictionary<byte, Brush> NucleotideColorScheme = new Dictionary<byte, Brush>();

        byte[] SequenceData;

        const int RECT_DIMM = 16;
        int RectangleDimension = RECT_DIMM;
        int RowGapHeight = 32;
        int AnnotationHeight = 15;
        int AnnotationGapHeight = 1;

        int NumberOfBasesPerLine = 0;

        LinearGradientBrush AnnotationBrush = new LinearGradientBrush(
                new GradientStopCollection(new List<GradientStop>() {
                    new GradientStop(Colors.LemonChiffon, 0.0),
                    new GradientStop(Colors.LemonChiffon, 0.5),
                    new GradientStop(Color.FromArgb(255,255,225,127), 0.5),
                    new GradientStop(Colors.LemonChiffon, 1.0)}), 90.0);

        LinearGradientBrush AnnotationSelectedBrush = new LinearGradientBrush(
                new GradientStopCollection(new List<GradientStop>() {
                    new GradientStop(Color.FromArgb(127,170,215,230), 0.0),
                    new GradientStop(Color.FromArgb(127,170,215,230), 0.5),
                    new GradientStop(Colors.LightBlue, 0.5),
                    new GradientStop(Color.FromArgb(127,170,215,230), 1.0)}), 90.0);

        LinearGradientBrush ABrush = new LinearGradientBrush(
                new GradientStopCollection(new List<GradientStop>() {
                    new GradientStop(Color.FromArgb(175, 65, 105, 225), 0.0),
                    new GradientStop(Color.FromArgb(175, 65, 105, 225), 0.5),
                    new GradientStop(Color.FromArgb(255, 65, 105, 225), 0.5),
                    new GradientStop(Color.FromArgb(127, 65, 105, 225), 1.0)}), 90.0);

        LinearGradientBrush CBrush = new LinearGradientBrush(
                new GradientStopCollection(new List<GradientStop>() {
                    new GradientStop(Color.FromArgb(127, 255, 165, 0), 0.0),
                    new GradientStop(Color.FromArgb(127, 255, 165, 0), 0.5),
                    new GradientStop(Color.FromArgb(255, 255, 165, 0), 0.5),
                    new GradientStop(Color.FromArgb(127, 255, 165, 0), 1.0)}), 90.0);

        LinearGradientBrush GBrush = new LinearGradientBrush(
                new GradientStopCollection(new List<GradientStop>() {
                    new GradientStop(Color.FromArgb(150, 255, 69, 0), 0.0),
                    new GradientStop(Color.FromArgb(150, 255, 69, 0), 0.5),
                    new GradientStop(Color.FromArgb(255, 255, 69, 0), 0.5),
                    new GradientStop(Color.FromArgb(127, 255, 69, 0), 1.0)}), 90.0);

        LinearGradientBrush TBrush = new LinearGradientBrush(
                new GradientStopCollection(new List<GradientStop>() {
                    new GradientStop(Color.FromArgb(63, 0, 250, 154), 0.0),
                    new GradientStop(Color.FromArgb(63, 0, 250, 154), 0.5),
                    new GradientStop(Color.FromArgb(255, 0, 250, 154), 0.5),
                    new GradientStop(Color.FromArgb(63, 0, 250, 154), 1.0)}), 90.0);

        Line LocationCursor = null;
        Line LocationCursorTop = null;
        Line LocationCursorBottom = null;


        bool SelectionMode = false;

        Polygon AreaBeforeSelection, AreaAfterSelection;

        Line SelectionStartMark, SelectionEndMark;

        int MaxFeaturesDepth = 0;

        List<FeatureItem>[] AnnotationLines = null;

        Dictionary<FeatureItem, List<Rectangle>> AnnotationRectanglesMap = new Dictionary<FeatureItem, List<Rectangle>>();
        Dictionary<Rectangle, FeatureItem> RectangleAnnotationMap = new Dictionary<Rectangle, FeatureItem>();
        Dictionary<Rectangle, TextBlock> RectangleTextBlockMap = new Dictionary<Rectangle, TextBlock>();

        List<Rectangle> SelectedRectangles;
        FeatureItem SelectedAnnotation = null;

        bool AnnotationSelected = false;

        bool addMode = true;

        double mouseDownX = 0.0;
        double mouseDownY = 0.0;

        int totalPages = 0;
        int currentPage = 1;
        int pageSize = 1000;

        delegate void LoadSequenceDelegate(string file);
        delegate void HideScreenDelegate();

        public MainWindow()
        {
            InitializeComponent();

            NucleotideColorScheme.Add(65, ABrush);
            NucleotideColorScheme.Add(67, CBrush);
            NucleotideColorScheme.Add(71, GBrush);
            NucleotideColorScheme.Add(84, TBrush);

            NucleotideColorScheme.Add(97, ABrush);
            NucleotideColorScheme.Add(99, CBrush);
            NucleotideColorScheme.Add(103, GBrush);
            NucleotideColorScheme.Add(116, TBrush);

            AnnotationHeight = RectangleDimension - AnnotationGapHeight;

            LocationCursor = new Line();
            LocationCursor.Stroke = new SolidColorBrush(Colors.Red);
            LocationCursor.StrokeThickness = 1;

            LocationCursorTop = new Line();
            LocationCursorTop.Stroke = new SolidColorBrush(Colors.Red);
            LocationCursorTop.StrokeThickness = 1;

            LocationCursorBottom = new Line();
            LocationCursorBottom.Stroke = new SolidColorBrush(Colors.Red);
            LocationCursorBottom.StrokeThickness = 1;

            SelectionStartMark = new Line();
            SelectionStartMark.Stroke = new SolidColorBrush(Colors.Green);
            SelectionStartMark.StrokeThickness = 3;

            SelectionEndMark = new Line();
            SelectionEndMark.Stroke = new SolidColorBrush(Colors.Green);
            SelectionEndMark.StrokeThickness = 3;

            AreaBeforeSelection = new Polygon();
            AreaBeforeSelection.Points = new PointCollection();
            AreaAfterSelection = new Polygon();
            AreaAfterSelection.Points = new PointCollection();
            AreaBeforeSelection.Fill = new SolidColorBrush(Color.FromArgb(160, 255, 255, 255));
            AreaAfterSelection.Fill = new SolidColorBrush(Color.FromArgb(160, 255, 255, 255));
        }

        ISequence CurrentSequence { get; set; }

        private void DrawNucleotide(int row, int col, int width, int height, byte nucleotide)
        {
            TextBlock tb = new TextBlock();
            mainCanvas.Children.Add(tb);

            Canvas.SetTop(tb, row);
            Canvas.SetLeft(tb, col);
            tb.Width = width;
            tb.Height = height;
            tb.TextAlignment = TextAlignment.Center;
            tb.Text = ((char)nucleotide).ToString();
            tb.FontSize = 0.7 * height;
            tb.Background = NucleotideColorScheme[nucleotide];
        }

        private void DrawSequence(int start, int end)
        {
            int row = 0;
            int col = 0;

            int x = 0;
            int y = 0;

            int i = 0;

            for (int b = start - 1; b < end; b++)
            {
                i++;
                x = row * (RectangleDimension + RowGapHeight) + RectangleDimension;
                y = col * RectangleDimension;
                DrawNucleotide(x, y, RectangleDimension, RectangleDimension, SequenceData[b]);
                col++;

                if (i % (100 * 10 / Convert.ToInt32(textBox1.Text)) == 1)
                {
                    TextBlock tb = new TextBlock();
                    tb.Height = RectangleDimension;
                    tb.Text = (start + i - 1).ToString();
                    tb.FontSize = 0.6 * RectangleDimension;
                    mainCanvas.Children.Add(tb);
                    Canvas.SetTop(tb, x - RectangleDimension);
                    Canvas.SetLeft(tb, y);

                    Line l = new Line();
                    l.Stroke = new SolidColorBrush(Colors.Black);
                    l.StrokeThickness = 1;
                    mainCanvas.Children.Add(l);
                    Canvas.SetTop(l, x);
                    Canvas.SetLeft(l, y + RectangleDimension / 2);
                    l.X2 = l.X1;
                    l.Y2 = l.Y1 - 0.2 * RectangleDimension;
                }

                if (col >= NumberOfBasesPerLine)
                {
                    col = 0;
                    row++;
                }
            }

            mainCanvas.Height = x + RowGapHeight; // + RectangleDimension 
        }

        private void btnLoadSequence_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.RestoreDirectory = true;
            ofd.Multiselect = false;
            ofd.Filter = "Genbank File (*.gbk) | *.gbk";

            if ((bool)ofd.ShowDialog())
            {
                string fileName = ofd.FileName;

                LoadSequence(fileName);

                Viewer.Visibility = System.Windows.Visibility.Visible;
                FeaturePanel.Visibility = System.Windows.Visibility.Visible;
                ButtonsPanels.Visibility = System.Windows.Visibility.Visible;
            }
        }

        private void LoadSequence(string fileName)
        {
            ISequenceParser parser = new GenBankParser(fileName);

            try
            {
                var sequenceList = parser.Parse();

                if (sequenceList != null)
                {
                    foreach (ISequence seq in sequenceList)
                    {
                        if (seq.Alphabet != Alphabets.DNA && seq.Alphabet != Alphabets.RNA)
                        {
                            MessageBox.Show("The selected sequence must be either DNA or RNA", SequenceViewer.Properties.Resources.ApplicationName);
                            break;
                        }

                        CurrentSequence = seq;
                        SequenceData = seq.Select(b => b).ToArray();

                        totalPages = SequenceData.Length / pageSize + 1;
                        tbTotalPages.Text = totalPages.ToString();

                        this.Cursor = Cursors.Wait;

                        RepaintCanvas(true, 1, pageSize);

                        this.Cursor = Cursors.Arrow;

                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, SequenceViewer.Properties.Resources.ApplicationName);
            }

            btnSaveSequence.IsEnabled = true;
            btnAddAnnotation.IsEnabled = true;
        }

        private void RepaintCanvas(bool reloadFeatures, int start, int end)
        {
            DeleteChildrenRecursive(mainCanvas);

            if (end > SequenceData.Length)
                end = SequenceData.Length;

            if (reloadFeatures)
            {
                GetSequenceFeatures(start, end);
            }

            PopulateFeatureList(start, end);

            DrawCanvasBackground();
            DrawSequence(start, end);
            DrawSequenceFeatures(start, end);
        }

        private void DrawCanvasBackground()
        {
            int canvasHeight = (pageSize / NumberOfBasesPerLine) * (RectangleDimension + RowGapHeight) +
                (pageSize % NumberOfBasesPerLine > 0 ? RectangleDimension + RowGapHeight : 0) +
                RectangleDimension;

            for (int i = 0; i < mainCanvas.Width; i += RectangleDimension)
            {
                Line l = new Line();
                mainCanvas.Children.Add(l);
                l.X1 = i;
                l.Y1 = 0;
                l.X2 = i;
                l.Y2 = canvasHeight;
                l.StrokeThickness = 0.25;
                l.Stroke=new SolidColorBrush(Colors.Gray);
            }

            for (int i = 0; i < canvasHeight; i += RectangleDimension)
            {
                Line l = new Line();
                mainCanvas.Children.Add(l);
                l.X1 = 0;
                l.Y1 = i;
                l.X2 = mainCanvas.Width;
                l.Y2 = i;
                l.StrokeThickness = 0.25;
                l.Stroke = new SolidColorBrush(Colors.Gray);
            }
        }

        private void ShowWaitScreen()
        {
            WaitBorder.Visibility = System.Windows.Visibility.Visible;
            WaitSign.Visibility = System.Windows.Visibility.Visible;
        }

        private void HideWaitScreen()
        {
            WaitBorder.Visibility = System.Windows.Visibility.Hidden;
            WaitSign.Visibility = System.Windows.Visibility.Hidden;
        }

        private void DrawSequenceFeatures(int start, int end)
        {
            AnnotationRectanglesMap.Clear();

            for (int i = 0; i < AnnotationLines.Length; i++)
            {
                foreach (var annotation in AnnotationLines[i])
                {
                    int annotationLength = ((annotation.Location.LocationEnd > end)
                                            ? end
                                            : annotation.Location.LocationEnd) -
                                            ((annotation.Location.LocationStart < start)
                                            ? start
                                            : annotation.Location.LocationStart) + 1;
                    int annotationStart = (annotation.Location.LocationStart < start)
                                            ? 0
                                            : (annotation.Location.LocationStart - start + 1) % NumberOfBasesPerLine - 1;
                    int lineNum = (annotation.Location.LocationStart < start)
                                  ? 0
                                  : (annotation.Location.LocationStart - start) / NumberOfBasesPerLine;

                    while (true)
                    {
                        Rectangle r = new Rectangle();
                        r.RadiusX = r.RadiusY = 5;
                        r.Fill = AnnotationBrush;
                        r.Stroke = new SolidColorBrush(Colors.Black);
                        r.StrokeThickness = 0.25;
                        mainCanvas.Children.Add(r);
                        r.Height = AnnotationHeight;

                        TextBlock tb = new TextBlock();
                        tb.Foreground = new SolidColorBrush(Colors.Maroon);
                        mainCanvas.Children.Add(tb);
                        tb.Height = AnnotationHeight;
                        tb.Text = string.IsNullOrEmpty(annotation.Label.Trim()) ? annotation.Key : annotation.Label;
                        tb.FontSize = 0.7 * AnnotationHeight;
                        tb.Padding = new Thickness(5, 0, 0, 0);
                        tb.Tag = r;

                        ToolTip tt = new ToolTip();
                        tt.Background = new SolidColorBrush(Color.FromArgb(128, 0, 50, 200));
                        tt.Foreground = new SolidColorBrush(Colors.White);
                        StringBuilder sb = new StringBuilder();
                        sb.AppendLine("Label: " + annotation.Label);
                        sb.AppendLine("Length: " + (annotation.Location.LocationEnd - annotation.Location.LocationStart + 1).ToString());
                        sb.AppendLine("Interval: " + annotation.Location.LocationStart.ToString() + ".." + annotation.Location.LocationEnd.ToString());
                        foreach (KeyValuePair<string, List<string>> kvp in annotation.Qualifiers)
                        {
                            if (kvp.Value.Count == 1)
                            {
                                sb.AppendLine(kvp.Key + ": " + (kvp.Value[0].Length > 50 ? kvp.Value[0].Substring(0, 47) + "..." : kvp.Value[0]));
                            }
                            else if (kvp.Value.Count > 1)
                            {
                                string tmp = kvp.Value[0];
                                for (int j = 1; j < kvp.Value.Count; j++)
                                    tmp += ", " + kvp.Value[j];
                                sb.AppendLine(kvp.Key + ": " + (tmp.Length > 50 ? tmp.Substring(0, 47) + "..." : tmp));
                            }
                        }

                        tt.Content = sb.ToString();
                        tb.ToolTip = tt;

                        Canvas.SetTop(r, lineNum * (RowGapHeight + RectangleDimension) + 2 * RectangleDimension + i * (AnnotationHeight + AnnotationGapHeight) + AnnotationGapHeight);
                        Canvas.SetLeft(r, annotationStart * RectangleDimension);

                        Canvas.SetTop(tb, lineNum * (RowGapHeight + RectangleDimension) + 2 * RectangleDimension + i * (AnnotationHeight + AnnotationGapHeight) + AnnotationGapHeight);
                        Canvas.SetLeft(tb, annotationStart * RectangleDimension);

                        if (!AnnotationRectanglesMap.ContainsKey(annotation))
                            AnnotationRectanglesMap[annotation] = new List<Rectangle>();
                        AnnotationRectanglesMap[annotation].Add(r);
                        RectangleAnnotationMap.Add(r, annotation);
                        RectangleTextBlockMap.Add(r, tb);

                        tb.MouseDown += new MouseButtonEventHandler(tb_MouseDown);

                        if (annotationStart + annotationLength > NumberOfBasesPerLine)
                        {
                            r.Width = tb.Width = (NumberOfBasesPerLine - annotationStart) * RectangleDimension;
                            annotationLength -= NumberOfBasesPerLine - annotationStart;
                            annotationStart = 0;
                            lineNum++;
                        }
                        else
                        {
                            r.Width = tb.Width = annotationLength * RectangleDimension;
                            break;
                        }
                    }
                }
            }

            mainCanvas.Children.Add(LocationCursor);
            mainCanvas.Children.Add(LocationCursorTop);
            mainCanvas.Children.Add(LocationCursorBottom);
        }

        void tb_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.RightButton == MouseButtonState.Pressed)
                return;

            UnselectAnnotation();

            SelectedRectangles = AnnotationRectanglesMap[RectangleAnnotationMap[(Rectangle)((TextBlock)sender).Tag]];

            foreach (Rectangle r in SelectedRectangles)
            {
                r.StrokeThickness = 0.25;
                r.Fill = AnnotationSelectedBrush;
            }

            AnnotationSelected = true;

            SelectedAnnotation = RectangleAnnotationMap[(Rectangle)((TextBlock)sender).Tag];

            btnEditAnnotation.IsEnabled = true;
            btnDeleteAnnotation.IsEnabled = true;
        }

        private int GetMaxFeaturesDepth(List<FeatureItem> features, int start, int end)
        {
            if (features.Count == 0)
                return 0;

            SortedList<int, int> locations = new SortedList<int, int>();

            foreach (FeatureItem fi in features)
            {
                locations[fi.Location.LocationStart] = 0;
                locations[fi.Location.LocationEnd] = 0;
            }

            SortedList<int, int> locations2 = new SortedList<int, int>(locations);
            bool startFound = false;

            foreach (FeatureItem fi in features)
            {
                startFound = false;
                foreach (KeyValuePair<int, int> kvp in locations2)
                {
                    if (!startFound)
                    {
                        if (fi.Location.LocationStart <= kvp.Key && fi.Location.LocationEnd >= kvp.Key)
                        {
                            locations[kvp.Key]++;
                            startFound = true;
                        }

                        continue;
                    }

                    if (fi.Location.LocationEnd >= kvp.Key)
                        locations[kvp.Key]++;
                    else
                        break;
                }
            }

            return (from r in locations.Values
                    select r).Max();
        }

        private void GetSequenceFeatures(int start, int end)
        {
            var features = (from f in ((GenBankMetadata)CurrentSequence.Metadata[Bio.Util.Helper.GenBankMetadataKey]).Features.All
                            where !(f.Location.LocationStart > end || f.Location.LocationEnd < start) && f.Key != "source"
                            select f).ToList();

            MaxFeaturesDepth = GetMaxFeaturesDepth(features, start, end);

            RowGapHeight = MaxFeaturesDepth * (AnnotationHeight + AnnotationGapHeight) + RectangleDimension;
            NumberOfBasesPerLine = Convert.ToInt32(Math.Ceiling(mainCanvas.Width / RectangleDimension)) - 1;

            AnnotationLines = new List<FeatureItem>[MaxFeaturesDepth];

            foreach (var feature in features)
            {
                for (int i = 0; i < MaxFeaturesDepth; i++)
                {
                    bool canBeInserted = true;

                    if (AnnotationLines[i] == null)
                    {
                        AnnotationLines[i] = new List<FeatureItem>();
                    }
                    else
                    {
                        foreach (FeatureItem annt in AnnotationLines[i])
                        {
                            if (!(feature.Location.LocationEnd < annt.Location.LocationStart || 
                                feature.Location.LocationStart > annt.Location.LocationEnd))
                            {
                                canBeInserted = false;
                                break;
                            }
                        }
                    }

                    if (canBeInserted)
                    {
                        AnnotationLines[i].Add(feature);
                        break;
                    }
                }
            }
        }

        private void PopulateFeatureList(int start, int end)
        {
            var features = from f in ((GenBankMetadata)CurrentSequence.Metadata[Bio.Util.Helper.GenBankMetadataKey]).Features.All
                           where !(f.Location.LocationStart > end || f.Location.LocationEnd < start)
                                && f.Key != "source"
                           select f;

            lstSeqFeatures.ItemsSource = features;
            chkSelectAll.Checked -= SelectAll_Checked;
            chkSelectAll.IsChecked = true;
            chkSelectAll.Checked += SelectAll_Checked;
        }

        private void textBox1_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Return)
            {
                int zoom;
                if (!int.TryParse(textBox1.Text, out zoom))
                    zoom = 100;

                if (zoom > 200)
                    zoom = 200;

                if (zoom < 20)
                    zoom = 20;

                textBox1.Text = zoom.ToString();

                if (SequenceData != null)
                {
                    RectangleDimension = Convert.ToInt32(RECT_DIMM * zoom / 100);
                    AnnotationHeight = RectangleDimension - AnnotationGapHeight;
                    RowGapHeight = MaxFeaturesDepth * (AnnotationHeight + AnnotationGapHeight) + RectangleDimension;
                    NumberOfBasesPerLine = Convert.ToInt32(Math.Ceiling(mainCanvas.Width / RectangleDimension)) - 1;

                    this.Cursor = Cursors.Wait;

                    RepaintCanvas(false, (currentPage - 1) * pageSize + 1, currentPage * pageSize);

                    this.Cursor = Cursors.Arrow;
                }
            }
        }

        private void mainCanvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (CurrentSequence == null)
                return;
            
            double x = e.GetPosition(mainCanvas).X;
            double y = e.GetPosition(mainCanvas).Y;

            if (e.LeftButton == MouseButtonState.Pressed)
            {
                SelectionEndMark.X1 = x - x % RectangleDimension;
                SelectionEndMark.Y1 = y - y % (RectangleDimension + RowGapHeight);
                SelectionEndMark.X2 = SelectionEndMark.X1;
                SelectionEndMark.Y2 = SelectionEndMark.Y1 + RectangleDimension + RowGapHeight;

                if (SelectionEndMark.Y1 < SelectionStartMark.Y1 ||
                    (SelectionEndMark.Y1 == SelectionStartMark.Y1 && SelectionEndMark.X1 < SelectionStartMark.X1))
                {
                    mainCanvas.Children.Remove(AreaBeforeSelection);
                    mainCanvas.Children.Remove(AreaAfterSelection);
                    mainCanvas.Children.Remove(SelectionStartMark);
                    mainCanvas.Children.Remove(SelectionEndMark);

                    SelectionMode = false;

                    return;
                }

                AreaAfterSelection.Points.Clear();
                AreaAfterSelection.Points.Add(new Point(x - x % RectangleDimension, y - y % (RectangleDimension + RowGapHeight)));
                AreaAfterSelection.Points.Add(new Point(mainCanvas.Width, y - y % (RectangleDimension + RowGapHeight)));
                AreaAfterSelection.Points.Add(new Point(mainCanvas.Width, mainCanvas.Height)); // - RectangleDimension
                AreaAfterSelection.Points.Add(new Point(0, mainCanvas.Height)); //  - RectangleDimension
                AreaAfterSelection.Points.Add(new Point(0, y - y % (RectangleDimension + RowGapHeight) + (RectangleDimension + RowGapHeight)));
                AreaAfterSelection.Points.Add(new Point(x - x % RectangleDimension, y - y % (RectangleDimension + RowGapHeight) + (RectangleDimension + RowGapHeight)));


                if (!SelectionMode)
                {
                    if (x != mouseDownX || y != mouseDownY)
                    {
                        mainCanvas.Children.Add(SelectionStartMark);
                        mainCanvas.Children.Add(AreaBeforeSelection);
                        mainCanvas.Children.Add(SelectionEndMark);
                        mainCanvas.Children.Add(AreaAfterSelection);
                        SelectionMode = true;
                    }
                }
            }
            else
            {
                LocationCursor.X1 = x - x % RectangleDimension;
                LocationCursor.Y1 = y - y % (RectangleDimension + RowGapHeight);
                LocationCursor.X2 = LocationCursor.X1;
                LocationCursor.Y2 = LocationCursor.Y1 + RectangleDimension + RowGapHeight;

                LocationCursorTop.X1 = LocationCursor.X1 - RectangleDimension * 1.0 / 2;
                LocationCursorTop.Y1 = LocationCursorTop.Y2 = LocationCursor.Y1;
                LocationCursorTop.X2 = LocationCursor.X1 + RectangleDimension * 1.0 / 2;

                LocationCursorBottom.X1 = LocationCursor.X1 - RectangleDimension * 1.0 / 2;
                LocationCursorBottom.Y1 = LocationCursorBottom.Y2 = LocationCursor.Y2;
                LocationCursorBottom.X2 = LocationCursor.X1 + RectangleDimension * 1.0 / 2;
            }
        }

        private void mainCanvas_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (!SelectionMode)
            {
                mainCanvas.Children.Remove(AreaBeforeSelection);
                mainCanvas.Children.Remove(AreaAfterSelection);
                mainCanvas.Children.Remove(SelectionStartMark);
                mainCanvas.Children.Remove(SelectionEndMark);

                double x = e.GetPosition(mainCanvas).X;
                double y = e.GetPosition(mainCanvas).Y;
            }

            SelectionMode = false;
        }

        private void mainCanvas_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!AnnotationSelected)
            {
                UnselectAnnotation();
                SelectedAnnotation = null;

                btnEditAnnotation.IsEnabled = false;
                btnDeleteAnnotation.IsEnabled = false;
            }

            if (e.RightButton == MouseButtonState.Pressed || CurrentSequence == null)
            {
                return;
            }

            mainCanvas.Children.Remove(AreaBeforeSelection);
            mainCanvas.Children.Remove(AreaAfterSelection);
            mainCanvas.Children.Remove(SelectionStartMark);
            mainCanvas.Children.Remove(SelectionEndMark);

            double x = e.GetPosition(mainCanvas).X;
            double y = e.GetPosition(mainCanvas).Y;

            mouseDownX = x;
            mouseDownY = y;

            SelectionStartMark.X1 = x - x % RectangleDimension;
            SelectionStartMark.Y1 = y - y % (RectangleDimension + RowGapHeight);
            SelectionStartMark.X2 = SelectionStartMark.X1;
            SelectionStartMark.Y2 = SelectionStartMark.Y1 + RectangleDimension + RowGapHeight;

            AreaBeforeSelection.Points.Clear();
            AreaBeforeSelection.Points.Add(new Point(0, 0));
            AreaBeforeSelection.Points.Add(new Point(mainCanvas.Width, 0));
            AreaBeforeSelection.Points.Add(new Point(mainCanvas.Width, y - y % (RectangleDimension + RowGapHeight)));
            AreaBeforeSelection.Points.Add(new Point(x - x % RectangleDimension, y - y % (RectangleDimension + RowGapHeight)));
            AreaBeforeSelection.Points.Add(new Point(x - x % RectangleDimension, y - y % (RectangleDimension + RowGapHeight) + (RectangleDimension + RowGapHeight)));
            AreaBeforeSelection.Points.Add(new Point(0, y - y % (RectangleDimension + RowGapHeight) + (RectangleDimension + RowGapHeight)));

            AnnotationSelected = false;
        }

        private void UnselectAnnotation()
        {
            if (SelectedRectangles != null)
            {
                foreach (Rectangle r in SelectedRectangles)
                {
                    r.StrokeThickness = 0.25;
                    r.Fill = AnnotationBrush;
                }
            }
        }

        private void CheckBox_Checked(object sender, RoutedEventArgs e)
        {
            FeatureItem fi = ((CheckBox)sender).DataContext as FeatureItem;

            if ((bool)((CheckBox)sender).IsChecked)
            {
                foreach (Rectangle r in AnnotationRectanglesMap[fi])
                {
                    mainCanvas.Children.Add(r);
                    mainCanvas.Children.Add(RectangleTextBlockMap[r]);
                }

                mainCanvas.Children.Remove(LocationCursor);
                mainCanvas.Children.Add(LocationCursor);
            }
            else
            {
                foreach (Rectangle r in AnnotationRectanglesMap[fi])
                {
                    mainCanvas.Children.Remove(r);
                    mainCanvas.Children.Remove(RectangleTextBlockMap[r]);
                }
            }
        }

        public FeatureItem CurrentFeatureItem { get; set; }

        private void btnAddAnnotation_Click(object sender, RoutedEventArgs e)
        {
            addMode = true;
            mainGrid.IsEnabled = false;
            mainBorder.Visibility = System.Windows.Visibility.Visible;
            AnnotationMasterControl.Visibility = System.Windows.Visibility.Visible;

            if (mainCanvas.Children.Contains(SelectionStartMark))
            {
                int start, end;
                start = end = (currentPage - 1) * pageSize;
                start += (Convert.ToInt32(SelectionStartMark.Y1) / (RectangleDimension + RowGapHeight)) * NumberOfBasesPerLine + Convert.ToInt32(SelectionStartMark.X1) / RectangleDimension + 1;
                end += (Convert.ToInt32(SelectionEndMark.Y1) / (RectangleDimension + RowGapHeight)) * NumberOfBasesPerLine + Convert.ToInt32(SelectionEndMark.X1) / RectangleDimension;

                AnnotationMasterControl.Initialize(addMode, this, start, end, CurrentSequence.Count, null);
            }
            else
                AnnotationMasterControl.Initialize(addMode, this, 0, 0, CurrentSequence.Count, null);
        }

        void AnnotationMasterControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue == false)
            {
                mainGrid.IsEnabled = true;
                mainBorder.Visibility = System.Windows.Visibility.Hidden;

                if (CurrentFeatureItem != null)
                {
                    if (addMode)
                        ((GenBankMetadata)CurrentSequence.Metadata[Bio.Util.Helper.GenBankMetadataKey]).Features.All.Add(StandardFeatureMap.GetStandardFeatureItem(CurrentFeatureItem));

                    this.Cursor = Cursors.Wait;

                    RepaintCanvas(true, (currentPage - 1) * pageSize + 1, currentPage * pageSize);

                    this.Cursor = Cursors.Arrow;
                }
            }
        }

        void DeleteChildrenRecursive(Panel panel)
        {
            foreach (UIElement uie in panel.Children)
            {
                if (uie is Panel)
                {
                    DeleteChildrenRecursive(uie as Panel);
                }
            }

            panel.Children.Clear();
        }

        private void btnSaveSequence_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.RestoreDirectory = true;
            sfd.Filter = "Genbank File (*.gbk) | *.gbk";

            if ((bool)sfd.ShowDialog())
            {
                GenBankFormatter formatter = new GenBankFormatter(sfd.FileName);
                formatter.Write(CurrentSequence);

                MessageBox.Show("Save Complete.", SequenceViewer.Properties.Resources.ApplicationName);
            }
        }

        private void btnEditAnnotation_Click(object sender, RoutedEventArgs e)
        {
            addMode = false;
            mainGrid.IsEnabled = false;
            mainBorder.Visibility = System.Windows.Visibility.Visible;
            AnnotationMasterControl.Visibility = System.Windows.Visibility.Visible;

            AnnotationMasterControl.Initialize(
                addMode, 
                this,
                SelectedAnnotation.Location.LocationStart,
                SelectedAnnotation.Location.LocationEnd, 
                CurrentSequence.Count, 
                SelectedAnnotation);
        }

        private void btnDeleteAnnotation_Click(object sender, RoutedEventArgs e)
        {
            ((GenBankMetadata)CurrentSequence.Metadata[Bio.Util.Helper.GenBankMetadataKey]).Features.All.Remove(SelectedAnnotation);

            this.Cursor = Cursors.Wait;

            RepaintCanvas(true, (currentPage - 1) * pageSize + 1, currentPage * pageSize);

            this.Cursor = Cursors.Arrow;
        }

        private void txtPage_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Return)
            {
                int page;
                if (!int.TryParse(txtPage.Text, out page))
                {
                    page = currentPage;
                    return;
                }

                if (page > totalPages)
                    page = totalPages;

                if (page < 1)
                    page = 1;

                txtPage.Text = page.ToString();

                currentPage = page;

                RepaintCanvas(true, (currentPage - 1) * pageSize + 1, currentPage * pageSize);

                ResetPageLinks();
            }
        }

        private void Navigation_Click(object sender, RoutedEventArgs e)
        {
            string navText = ((Run)((Hyperlink)sender).Inlines.FirstInline).Text;

            switch (navText)
            {
                case "<<":
                    currentPage = 1;
                    break;
                case "<":
                    currentPage--;
                    break;
                case ">":
                    currentPage++;
                    break;
                case ">>":
                    currentPage = totalPages;
                    break;
                default: return;
            }

            txtPage.Text = currentPage.ToString();
            RepaintCanvas(true, (currentPage - 1) * pageSize + 1, currentPage * pageSize);

            ResetPageLinks();
        }

        private void ResetPageLinks()
        {
            if (currentPage == 1)
            {
                hlFirst.IsEnabled = false;
                hlPrev.IsEnabled = false;
            }
            else
            {
                hlFirst.IsEnabled = true;
                hlPrev.IsEnabled = true;
            }

            if (currentPage == totalPages)
            {
                hlNext.IsEnabled = false;
                hlLast.IsEnabled = false;
            }
            else
            {
                hlNext.IsEnabled = true;
                hlLast.IsEnabled = true;
            }
        }

        private void txtPageSize_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Return)
            {
                int psize;
                if (!int.TryParse(txtPageSize.Text, out psize))
                {
                    psize = pageSize;
                    return;
                }

                if (psize > 10000)
                    psize = 10000;

                if (psize < 100)
                    psize = 100;

                txtPageSize.Text = psize.ToString();

                pageSize = psize;
                totalPages = SequenceData.Length / pageSize + 1;
                tbTotalPages.Text = totalPages.ToString();
                currentPage = 1;
                txtPage.Text = currentPage.ToString();

                RepaintCanvas(true, 1, pageSize);

                ResetPageLinks();
            }
        }

        private void SelectAll_Checked(object sender, RoutedEventArgs e)
        {
            foreach (var item in lstSeqFeatures.Items)
            {
                ListBoxItem listboxItem = (ListBoxItem)(lstSeqFeatures.ItemContainerGenerator.ContainerFromItem(item));
                ContentPresenter cp = FindVisualChild<ContentPresenter>(listboxItem);
                ((CheckBox)cp.ContentTemplate.FindName("checkbox", cp)).IsChecked = (bool)((CheckBox)sender).IsChecked;
            }
        }

        private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);

                if (child != null && child is childItem)
                    return (childItem)child;
                else
                {
                    childItem childOfChild = FindVisualChild<childItem>(child);
                    if (childOfChild != null)
                        return childOfChild;
                }
            }

            return null;
        }
    }
}
